package org.llc.oauthclient.config;

import lombok.extern.slf4j.Slf4j;

import org.llc.common.starter.handle.ClientScopeVoter;
import org.llc.common.starter.handle.AuthExceptionEntryPoint;
import org.llc.common.starter.handle.CustomAccessDeniedHandler;
import org.llc.common.starter.handle.UrlRoleAuthVoter;
import org.llc.common.starter.handle.UrlRolesFilterHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.llc.common.starter.util.UserUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 资源服务器配置
 *
 * @author llc
 * @date 2020/1/9 12:11
 * @since 1.0.0
 */
@Slf4j
@Configuration
@EnableResourceServer
@EnableConfigurationProperties({SecurityOauth2Properties.class,SecurityProperties.class})
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * 角色/权限 分隔符{@value}
     */
    private final static String TO = "2";

    /**
     * 无需授权即可访问的接口{@value}
     */
    private final static String UN_AUTHENTICATED = "unAuthenticated";

    /**
     * 指定角色{@value}
     */
    private final static String ROLE = "role";

    /**
     * 指定权限{@value}
     */
    private final static String AUTH = "auth";

    /**
     * 权限不足处理器
     */
    private final CustomAccessDeniedHandler customAccessDeniedHandler;

    /**
     * 注入配置的tokenStore
     */
    private final TokenStore tokenStore;

    /**
     * 用来解决匿名用户访问无权限资源时的异常
     */
    private final AuthExceptionEntryPoint authExceptionEntryPoint;

    /**
     * 动态权限处理器配置
     */
    private final UrlRolesFilterHandler urlRolesFilterHandler;

    /**
     * 认证配置
     */
    private final SecurityOauth2Properties securityOauth2Properties;

    /**
     * 用户认证信息工具类
     */
    private final UserUtils userUtils;

    private final SecurityProperties properties;

    public ResourceServerConfig(
            CustomAccessDeniedHandler customAccessDeniedHandler,
            TokenStore tokenStore,
            AuthExceptionEntryPoint authExceptionEntryPoint,
            @Autowired(required = false)
                    UrlRolesFilterHandler urlRolesFilterHandler,
            SecurityOauth2Properties securityOauth2Properties,
            UserUtils userUtils,
            SecurityProperties properties) {
        this.customAccessDeniedHandler = customAccessDeniedHandler;
        this.tokenStore = tokenStore;
        this.authExceptionEntryPoint = authExceptionEntryPoint;
        this.urlRolesFilterHandler = urlRolesFilterHandler;
        this.securityOauth2Properties = securityOauth2Properties;
        this.userUtils = userUtils;
        this.properties = properties;
    }


    /**
     * 资源服务配置
     *
     * @param resources 资源服务
     * @author llc
     * @date 2020/1/10 17:47
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        // 资源服务Id（简易配置文件形式管理）
        resources.resourceId(securityOauth2Properties.getClient())
                // 设置tokenStore
                .tokenStore(tokenStore).stateless(true)
                // 用来解决匿名用户访问无权限资源时的异常
                .authenticationEntryPoint(authExceptionEntryPoint);
    }

    /**
     * 配置资源拦截规则
     *
     * @param http 请求规则
     * @author llc
     * @date 2020/1/10 17:10
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

        // 设置不受保护路径 设置受保护路径需要的角色权限
        Map<String, List<String>> antMatchers = properties.getAntMatchers();
        if (antMatchers != null) {
            Set<String> keys = antMatchers.keySet();
            for (String key : keys) {
                if (key.equals(UN_AUTHENTICATED)) {
                    List<String> urls = antMatchers.get(key);
                    for (String url : urls) {
                        http.authorizeRequests().antMatchers(url).permitAll();
                    }
                }else{
                    String type = key.split(TO)[0];
                    String str = key.split(TO)[1];
                    List<String> urls = antMatchers.get(key);
                    if (type.equals(ROLE)) {
                        for (String url : urls) {
                            http.authorizeRequests().antMatchers(url).hasRole(str);
                        }
                    }
                    if (type.equals(AUTH)) {
                        for (String url : urls) {
                            http.authorizeRequests().antMatchers(url).hasAuthority(str);
                        }
                    }
                }
            }
        }

        // 配合测试的代码
        // /save/no不受保护
        // /user/save 受保护
        http.authorizeRequests()
                .antMatchers("/save/**").permitAll()
                .anyRequest().authenticated();

        // 权限不足处理器
        http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
        
        // todo 动态权限动态scope存在问题后续会进行解决
        // 如果开启动态权限 或者开启动态作用域
        if(securityOauth2Properties.isEnableDynamicAuth() || (securityOauth2Properties.isEnableDynamicScopeAuth()) && securityOauth2Properties.scopeRuleIsExist() ){
            // 动态url权限
            http.authorizeRequests().withObjectPostProcessor(new DefinedObjectPostProcessor());
        }
    }

    /**
     * 决策管理
     * <p>
     * AffirmativeBased – 任何一个AccessDecisionVoter返回同意则允许访问
     * ConsensusBased – 同意投票多于拒绝投票（忽略弃权回答）则允许访问
     * UnanimousBased – 每个投票者选择弃权或同意则允许访问
     * @param isEnableDynamicAuth         是否启用动态权限
     * @param isEnableDynamicScopeAuth    设置是否启用动态Scope
     * @return org.springframework.security.access.AccessDecisionManager
     * @author llc
     * @date 2020/1/20 16:09
     */
    private AccessDecisionManager accessDecisionManager(boolean isEnableDynamicAuth, boolean isEnableDynamicScopeAuth) {
        log.info("配置决策管理 ---> UrlRoleAuthHandler");
        List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
        decisionVoters.add(new WebExpressionVoter());
        decisionVoters.add(new AuthenticatedVoter());
        decisionVoters.add(new RoleVoter());
        /* 路由权限管理 */
        if(isEnableDynamicAuth){
            log.info("加入角色权限投票 ---> UrlRoleAuthHandler");
            decisionVoters.add(new UrlRoleAuthVoter(userUtils));
        }
        // scope投票
        if(isEnableDynamicScopeAuth){
            log.info("加入客户端scope投票 ---> ClientScopeVoter");
            decisionVoters.add(new ClientScopeVoter());
        }
        return new UnanimousBased(decisionVoters);
    }

    /**
     * 定义对象后处理器
     *
     * @author llc
     * @date 2020/1/9 12:11
     * @since 1.0.0
     */
    class DefinedObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
        @Override
        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
            // 设置是否启用动态权限
            boolean isEnableDynamicAuth = securityOauth2Properties.isEnableDynamicAuth();
            urlRolesFilterHandler.setEnableDynamicAuth(isEnableDynamicAuth);

            // 设置是否启用动态Scope
            boolean isEnableDynamicScopeAuth = securityOauth2Properties.isEnableDynamicScopeAuth();
            urlRolesFilterHandler.setEnableDynamicScopeAuth(isEnableDynamicScopeAuth);
            // 如果存在scope规则则配置
            Map<String, List<String>> stringListMap = securityOauth2Properties.getScopeRule();
            if(securityOauth2Properties.scopeRuleIsExist()){
                urlRolesFilterHandler.setScopeRule(stringListMap);
            }
            object.setSecurityMetadataSource(urlRolesFilterHandler);
            object.setAccessDecisionManager(accessDecisionManager(isEnableDynamicAuth,isEnableDynamicScopeAuth));
            return object;
        }
    }

}
